<template>
	<view class="lime-painter" ref="limepainter">
		<view v-if="canvasId && size" :style="styles">
			<!-- #ifndef APP-NVUE -->
			<canvas class="lime-painter__canvas" v-if="use2dCanvas" :id="canvasId" type="2d" :style="size"></canvas>
			<canvas class="lime-painter__canvas" v-else :id="canvasId" :canvas-id="canvasId" :style="size"
				:width="boardWidth * dpr" :height="boardHeight * dpr" :hidpi="hidpi"></canvas>

			<!-- #endif -->
			<!-- #ifdef APP-NVUE -->
			<web-view :style="size" ref="webview" src="/uni_modules/lime-painter/hybrid/html/index.html"
				class="lime-painter__canvas" @pagefinish="onPageFinish" @error="onError" @onPostMessage="onMessage">
			</web-view>
			<!-- #endif -->
		</view>
		<slot />
	</view>
</template>

<script>
import { parent } from '../common/relation'
import props from './props'
import { toPx, base64ToPath, pathToBase64, isBase64, sleep, getImageInfo } from './utils';
//  #ifndef APP-NVUE
import { canIUseCanvas2d, isPC } from './utils';
import Painter from './painter';
// import Painter from '@painter'
const nvue = {}
//  #endif
//  #ifdef APP-NVUE
import nvue from './nvue'
//  #endif
export default {
	name: 'lime-painter',
	mixins: [props, parent('painter'), nvue],
	data() {
		return {
			use2dCanvas: false,
			canvasHeight: 150,
			canvasWidth: null,
			parentWidth: 0,
			inited: false,
			progress: 0,
			firstRender: 0,
			done: false,
			tasks: []
		};
	},
	computed: {
		styles() {
			return `${this.size}${this.customStyle || ''};` + (this.hidden && 'position: fixed; left: 1500rpx;')
		},
		canvasId() {
			return `l-painter${this._ && this._.uid || this._uid}`
		},
		size() {
			if (this.boardWidth && this.boardHeight) {
				return `width:${this.boardWidth}px; height: ${this.boardHeight}px;`;
			}
		},
		dpr() {
			return this.pixelRatio || uni.getWindowInfo().pixelRatio;
		},
		boardWidth() {
			const { width = 0 } = (this.elements && this.elements.css) || this.elements || this
			const w = toPx(width || this.width)
			return w || Math.max(w, toPx(this.canvasWidth));
		},
		boardHeight() {
			const { height = 0 } = (this.elements && this.elements.css) || this.elements || this
			const h = toPx(height || this.height)
			return h || Math.max(h, toPx(this.canvasHeight));
		},
		hasBoard() {
			return this.board && Object.keys(this.board).length
		},
		elements() {
			return this.hasBoard ? this.board : JSON.parse(JSON.stringify(this.el))
		}
	},
	created() {
		this.use2dCanvas = this.type === '2d' && canIUseCanvas2d() && !isPC
	},
	async mounted() {
		await sleep(30)
		await this.getParentWeith()
		this.$nextTick(() => {
			setTimeout(() => {
				this.$watch('elements', this.watchRender, {
					deep: true,
					immediate: true
				});
			}, 30)
		})
	},
	// #ifdef VUE3
	unmounted() {
		this.done = false
		this.inited = false
		this.firstRender = 0
		this.progress = 0
		this.painter = null
		clearTimeout(this.rendertimer)
	},
	// #endif
	// #ifdef VUE2
	destroyed() {
		this.done = false
		this.inited = false
		this.firstRender = 0
		this.progress = 0
		this.painter = null
		clearTimeout(this.rendertimer)
	},
	// #endif
	methods: {
		async watchRender(val, old) {
			if (!val || !val.views || (!this.firstRender ? !val.views.length : !this.firstRender) || !Object.keys(val).length || JSON.stringify(val) == JSON.stringify(old)) return;
			this.firstRender = 1
			this.progress = 0
			this.done = false
			clearTimeout(this.rendertimer)
			this.rendertimer = setTimeout(() => {
				this.render(val);
			}, this.beforeDelay)
		},
		async setFilePath(path, param) {
			let filePath = path
			const { pathType = this.pathType } = param || this
			if (pathType == 'base64' && !isBase64(path)) {
				filePath = await pathToBase64(path)
			} else if (pathType == 'url' && isBase64(path)) {
				filePath = await base64ToPath(path)
			}
			if (param && param.isEmit) {
				this.$emit('success', filePath);
			}
			return filePath
		},
		async getSize(args) {
			const { width } = args.css || args
			const { height } = args.css || args
			if (!this.size) {
				if (width || height) {
					this.canvasWidth = width || this.canvasWidth
					this.canvasHeight = height || this.canvasHeight
					await sleep(30);
				} else {
					await this.getParentWeith()
				}
			}
		},
		canvasToTempFilePathSync(args) {
			// this.stopWatch && this.stopWatch()
			// this.stopWatch = this.$watch('done', (v) => {
			// 	if (v) {
			// 		this.canvasToTempFilePath(args)
			// 		this.stopWatch && this.stopWatch()
			// 	}
			// }, {
			// 	immediate: true
			// })
			this.tasks.push(args)
			if (this.done) {
				this.runTask()
			}
		},
		runTask() {
			while (this.tasks.length) {
				const task = this.tasks.shift()
				this.canvasToTempFilePath(task)
			}
		},
		// #ifndef APP-NVUE
		getParentWeith() {
			return new Promise(resolve => {
				uni.createSelectorQuery()
					.in(this)
					.select(`.lime-painter`)
					.boundingClientRect()
					.exec(res => {
						const { width, height } = res[0] || {}
						this.parentWidth = Math.ceil(width || 0)
						this.canvasWidth = this.parentWidth || 300
						this.canvasHeight = height || this.canvasHeight || 150
						resolve(res[0])
					})
			})
		},
		async render(args = {}) {
			if (!Object.keys(args).length) {
				return console.error('空对象')
			}
			this.progress = 0
			this.done = false
			// #ifdef APP-NVUE
			this.tempFilePath.length = 0
			// #endif
			await this.getSize(args)
			const ctx = await this.getContext();

			let {
				use2dCanvas,
				boardWidth,
				boardHeight,
				canvas,
				afterDelay
			} = this;
			if (use2dCanvas && !canvas) {
				return Promise.reject(new Error('canvas 没创建'));
			}
			this.boundary = {
				top: 0,
				left: 0,
				width: boardWidth,
				height: boardHeight
			};
			this.painter = null
			if (!this.painter) {
				const { width } = args.css || args
				const { height } = args.css || args
				if (!width && this.parentWidth) {
					Object.assign(args, { width: this.parentWidth })
				}
				const param = {
					context: ctx,
					canvas,
					width: boardWidth,
					height: boardHeight,
					pixelRatio: this.dpr,
					useCORS: this.useCORS,
					createImage: getImageInfo.bind(this),
					performance: this.performance,
					listen: {
						onProgress: (v) => {
							this.progress = v
							this.$emit('progress', v)
						},
						onEffectFail: (err) => {
							this.$emit('faill', err)
						}
					}
				}
				this.painter = new Painter(param)
			}
			try {
				// vue3 赋值给data会引起图片无法绘制
				const { width, height } = await this.painter.source(JSON.parse(JSON.stringify(args)))
				this.boundary.height = this.canvasHeight = height
				this.boundary.width = this.canvasWidth = width
				await sleep(this.sleep);
				await this.painter.render()
				await new Promise(resolve => this.$nextTick(resolve));
				if (!use2dCanvas) {
					await this.canvasDraw();
				}
				if (afterDelay && use2dCanvas) {
					await sleep(afterDelay);
				}
				this.$emit('done');
				this.done = true
				if (this.isCanvasToTempFilePath) {
					this.canvasToTempFilePath()
						.then(res => {
							this.$emit('success', res.tempFilePath)
						})
						.catch(err => {
							this.$emit('fail', new Error(JSON.stringify(err)));
						});
				}
				this.runTask()
				return Promise.resolve({
					ctx,
					draw: this.painter,
					node: this.node
				});
			} catch (e) {
				//TODO handle the exception
			}

		},
		canvasDraw(flag = false) {
			return new Promise((resolve, reject) => this.ctx.draw(flag, () => setTimeout(() => resolve(), this
				.afterDelay)));
		},
		async getContext() {
			if (!this.canvasWidth) {
				this.$emit('fail', 'painter no size')
				console.error('[lime-painter]: 给画板或父级设置尺寸')
				return Promise.reject();
			}
			if (this.ctx && this.inited) {
				return Promise.resolve(this.ctx);
			}
			const { type, use2dCanvas, dpr, boardWidth, boardHeight } = this;
			const _getContext = () => {
				return new Promise(resolve => {
					uni.createSelectorQuery()
						.in(this)
						.select(`#${this.canvasId}`)
						.boundingClientRect()
						.exec(res => {
							if (res) {
								const ctx = uni.createCanvasContext(this.canvasId, this);
								if (!this.inited) {
									this.inited = true;
									this.use2dCanvas = false;
									this.canvas = res;
								}

								// 钉钉小程序框架不支持 measureText 方法，用此方法 mock
								if (!ctx.measureText) {
									function strLen(str) {
										let len = 0;
										for (let i = 0; i < str.length; i++) {
											if (str.charCodeAt(i) > 0 && str.charCodeAt(i) < 128) {
												len++;
											} else {
												len += 2;
											}
										}
										return len;
									}
									ctx.measureText = text => {
										let fontSize = ctx.state && ctx.state.fontSize || 12;
										const font = ctx.__font
										if (font && fontSize == 12) {
											fontSize = parseInt(font.split(' ')[3], 10);
										}
										fontSize /= 2;
										return {
											width: strLen(text) * fontSize
										};
									}
								}

								// #ifdef MP-ALIPAY
								ctx.scale(dpr, dpr);
								// #endif
								this.ctx = ctx
								resolve(this.ctx);
							} else {
								console.error('[lime-painter] no node')
							}
						});
				});
			};
			if (!use2dCanvas) {
				return _getContext();
			}
			return new Promise(resolve => {
				uni.createSelectorQuery()
					.in(this)
					.select(`#${this.canvasId}`)
					.node()
					.exec(res => {
						let { node: canvas } = res && res[0] || {};
						if (canvas) {
							const ctx = canvas.getContext(type);
							if (!this.inited) {
								this.inited = true;
								this.use2dCanvas = true;
								this.canvas = canvas;
							}
							this.ctx = ctx
							resolve(this.ctx);
						} else {
							console.error('[lime-painter]: no size')
						}
					});
			});
		},
		canvasToTempFilePath(args = {}) {
			return new Promise(async (resolve, reject) => {
				const { use2dCanvas, canvasId, dpr, fileType, quality } = this;
				const success = async (res) => {
					try {
						const tempFilePath = await this.setFilePath(res.tempFilePath || res, args)
						const result = Object.assign(res, { tempFilePath })
						args.success && args.success(result)
						resolve(result)
					} catch (e) {
						this.$emit('fail', e)
					}
				}

				let { top: y = 0, left: x = 0, width, height } = this.boundary || this;
				// let destWidth = width * dpr;
				// let destHeight = height * dpr;
				// #ifdef MP-ALIPAY
				// width = destWidth;
				// height = destHeight;
				// #endif

				const copyArgs = Object.assign({
					// x,
					// y,
					// width,
					// height,
					// destWidth,
					// destHeight,
					canvasId,
					id: canvasId,
					fileType,
					quality,
				}, args, { success });
				// if(this.isPC || use2dCanvas) {
				// 	copyArgs.canvas = this.canvas
				// }
				if (use2dCanvas) {
					copyArgs.canvas = this.canvas
					try {
						// #ifndef MP-ALIPAY
						const oFilePath = this.canvas.toDataURL(`image/${args.fileType || fileType}`.replace(/pg/, 'peg'), args.quality || quality)
						if (/data:,/.test(oFilePath)) {
							uni.canvasToTempFilePath(copyArgs, this);
						} else {
							const tempFilePath = await this.setFilePath(oFilePath, args)
							args.success && args.success({ tempFilePath })
							resolve({ tempFilePath })
						}
						// #endif
						// #ifdef MP-ALIPAY
						this.canvas.toTempFilePath(copyArgs)
						// #endif
					} catch (e) {
						args.fail && args.fail(e)
						reject(e)
					}
				} else {
					// #ifdef MP-ALIPAY
					if (this.ctx.toTempFilePath) {
						// 钉钉
						const ctx = uni.createCanvasContext(canvasId);
						ctx.toTempFilePath(copyArgs);
					} else {
						my.canvasToTempFilePath(copyArgs);
					}
					// #endif
					// #ifndef MP-ALIPAY
					uni.canvasToTempFilePath(copyArgs, this);
					// #endif
				}
			})
		}
		// #endif
	}
};
</script>
<style>
.lime-painter,
.lime-painter__canvas {
	// #ifndef APP-NVUE
	width: 100%;
	// #endif
	// #ifdef APP-NVUE
	flex: 1;
	// #endif
}
</style>
